#include "StdAfx.h"

namespace CoreRay
{
	namespace DirectX
	{
		DirectXGeometryObject::DirectXGeometryObject(ID3D10Device* graphicsDevice, ID3D10Effect* shader, Mesh^ mesh)
		{
			this->graphicsDevice = graphicsDevice;
			this->ObjectVertexFormat = mesh->GeometryBuffer->VertexFormat;
			this->VertexCount = mesh->GeometryBuffer->IndexBuffer->Count;
			this->WorldMatrix = mesh->WorldMatrix;
			this->InverseWorldMatrix = mesh->InversedWorldMatrix;

			//Create the vertex buffer for the mesh object
			BinaryWriter^ binaryWriter = gcnew BinaryWriter(gcnew MemoryStream());

			try
			{
				for(int i=0;i<mesh->GeometryBuffer->VertexBuffer->Count;i++)
				{
					for(int j=0;j<this->ObjectVertexFormat->ElementsCount;j++)
					{
						if(this->ObjectVertexFormat[j]->Size == 12)
						{
							Vector3 vector3 = mesh->GeometryBuffer->VertexBuffer->GetVertexElement<Vector3>(i, this->ObjectVertexFormat[j]->Name);
							
							binaryWriter->Write(vector3.X);
							binaryWriter->Write(vector3.Y);
							binaryWriter->Write(vector3.Z);
						}
						
						else if(this->ObjectVertexFormat[j]->Size == 8)
						{
							Vector2 vector2 = mesh->GeometryBuffer->VertexBuffer->GetVertexElement<Vector2>(i, this->ObjectVertexFormat[j]->Name);
							
							binaryWriter->Write(vector2.X);
							binaryWriter->Write(vector2.Y);
						}
					}
				}

				this->VertexBuffer = this->CreateVertexBuffer(((MemoryStream^) binaryWriter->BaseStream)->ToArray(), this->ObjectVertexFormat->VertexSize);
			}

			finally
			{
				delete binaryWriter;
			}

			//Create the index buffer for the mesh object
			binaryWriter = gcnew BinaryWriter(gcnew MemoryStream());

			try
			{
				for(int i=0;i<mesh->GeometryBuffer->IndexBuffer->Count;i++)
				{
					int index = mesh->GeometryBuffer->IndexBuffer->GetVertexIndex(i);
					binaryWriter->Write(index);
				}

				this->IndexBuffer = this->CreateIndexBuffer(((MemoryStream^) binaryWriter->BaseStream)->ToArray());
			}

			finally
			{
				delete binaryWriter;
			}

			//Create the input layout for the buffer
			this->InputLayout = this->CreateInputLayout(mesh->GeometryBuffer->VertexFormat, shader);
		}

		DirectXGeometryObject::~DirectXGeometryObject()
		{
			// Call the object finalizer to release unmanaged resources
			this->!DirectXGeometryObject();
		}

		DirectXGeometryObject::!DirectXGeometryObject()
		{
			//Free the vertex buffer
			if(this->VertexBuffer)
			{
				this->VertexBuffer->Release();
				this->VertexBuffer = NULL;
			}

			//Free the index buffer
			if(this->IndexBuffer)
			{
				this->IndexBuffer->Release();
				this->IndexBuffer = NULL;
			}

			//Free the input layout
			if(this->InputLayout)
			{
				this->InputLayout->Release();
				this->InputLayout = NULL;
			}
		}

		ID3D10Buffer* DirectXGeometryObject::CreateVertexBuffer(array<byte>^ initialData, int elementSize)
		{
			ID3D10Buffer* vertexBuffer;

			//Create the buffer description structure
			D3D10_BUFFER_DESC bufferDescription;

			bufferDescription.Usage = D3D10_USAGE_IMMUTABLE;
			bufferDescription.ByteWidth = initialData->Length;
			bufferDescription.BindFlags = D3D10_BIND_VERTEX_BUFFER;
			bufferDescription.CPUAccessFlags = 0;
			bufferDescription.MiscFlags = 0;

			//Create the initial data description structure
			D3D10_SUBRESOURCE_DATA initialDataDescription;
			pin_ptr<byte> pinnedInitialData = &initialData[0];
			initialDataDescription.pSysMem = pinnedInitialData;

			//Create the buffer
			HRESULT hresult = this->graphicsDevice->CreateBuffer(&bufferDescription, &initialDataDescription, &vertexBuffer);

			if(FAILED(hresult))
			{
				throw gcnew Exception("Direct3D: " + hresult);
			}

			return vertexBuffer;
		}

		ID3D10Buffer* DirectXGeometryObject::CreateIndexBuffer(array<byte>^ initialData)
		{
			ID3D10Buffer* indexBuffer;

			//Create the buffer description structure
			D3D10_BUFFER_DESC bufferDescription;

			bufferDescription.Usage = D3D10_USAGE_IMMUTABLE;
			bufferDescription.ByteWidth = initialData->Length;
			bufferDescription.BindFlags = D3D10_BIND_INDEX_BUFFER;
			bufferDescription.CPUAccessFlags = 0;
			bufferDescription.MiscFlags = 0;

			//Create the initial data description structure
			D3D10_SUBRESOURCE_DATA initialDataDescription;
			pin_ptr<byte> pinnedInitialData = &initialData[0];
			initialDataDescription.pSysMem = pinnedInitialData;

			//Create the buffer
			HRESULT hresult = this->graphicsDevice->CreateBuffer(&bufferDescription, &initialDataDescription, &indexBuffer);

			if(FAILED(hresult))
			{
				throw gcnew Exception("Direct3D: " + hresult);
			}

			return indexBuffer;
		}

		ID3D10InputLayout* DirectXGeometryObject::CreateInputLayout(VertexFormat^ vertexFormat, ID3D10Effect* shader)
		{
			ID3D10InputLayout* inputLayout;

			//Get the main shader pass description
			D3D10_PASS_DESC passDescription;
			shader->GetTechniqueByIndex(0)->GetPassByIndex(0)->GetDesc(&passDescription);

			D3D10_INPUT_ELEMENT_DESC* inputLayoutDescription = this->GetInputElementDescription(vertexFormat);
			UINT numElements = vertexFormat->ElementsCount;

			HRESULT hresult = graphicsDevice->CreateInputLayout(inputLayoutDescription, numElements, passDescription.pIAInputSignature, passDescription.IAInputSignatureSize, &inputLayout);
			
			if(FAILED(hresult))
			{
				throw gcnew Exception("Direct3D: " + hresult);
			}

			return inputLayout;
		}

		D3D10_INPUT_ELEMENT_DESC* DirectXGeometryObject::GetInputElementDescription(VertexFormat^ vertexFormat)
		{
			int vertexElementCount = vertexFormat->ElementsCount;

			D3D10_INPUT_ELEMENT_DESC* inputLayoutDescription = new D3D10_INPUT_ELEMENT_DESC[vertexElementCount];

			int offset = 0;

			for(int i = 0; i < vertexElementCount; i++)
			{
				LPCSTR semanticName = (LPCSTR) Marshal::StringToHGlobalAnsi(vertexFormat[i]->Name).ToPointer();

				D3D10_INPUT_ELEMENT_DESC elementDescription;
				elementDescription.SemanticName = semanticName;
				elementDescription.SemanticIndex = 0;
				elementDescription.Format = this->ConvertVertexFormat(vertexFormat[i]->Size);
				elementDescription.InputSlot = 0;
				elementDescription.AlignedByteOffset = offset;
				elementDescription.InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA;
				elementDescription.InstanceDataStepRate = 0;

				inputLayoutDescription[i] = elementDescription;

				offset += vertexFormat[i]->Size;
			}

			return inputLayoutDescription;
		}

		DXGI_FORMAT DirectXGeometryObject::ConvertVertexFormat(int size)
		{
			if(size == 4)
			{
				return DXGI_FORMAT_R32_FLOAT;
			}

			else if(size == 8)
			{
				return DXGI_FORMAT_R32G32_FLOAT;
			}

			else if(size == 12)
			{
				return DXGI_FORMAT_R32G32B32_FLOAT;
			}

			else
			{
				return DXGI_FORMAT_R32G32B32A32_FLOAT;
			}
		}
	}
}
